#ifndef IP_ADDRESS_H
#define IP_ADDRESS_H

#include "endian_portable.h"
#include <algorithm>
#include <ostream>
#include <stdint.h>
#include <string.h>
#include <string>

/// @file

namespace secsmart_tcpip
{

// The implementation of the classes is based on document N4771 "Working Draft, C++ Extensions for Networking"
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/n4771.pdf

/**
 * @class IPv4Address
 * Represents an IPv4 address (of type XXX.XXX.XXX.XXX)
 */
class IPv4Address
{
  public:
    /**
     * A default constructor that creates an instance of the class with unspecified/zero address
     */
    IPv4Address() { m_addr32 = 0; }

    /**
     * A constructor that creates an instance of the class out of 4-byte integer value.
     * @param[in] addrAsInt The address as 4-byte integer in network byte order
     */
    IPv4Address(uint32_t addr) { m_addr32 = addr; }

    /**
     * A constructor that creates an instance of the class out of 4-byte array.
     * @param[in] bytes The address as 4-byte array in network byte order
     */
    IPv4Address(const uint8_t bytes[4]) { memcpy(m_addr8, bytes, sizeof(m_addr8)); }

    /**
     * A constructor that creates an instance of the class out of std::string value
     * If the string doesn't represent a valid IPv4 address, an instance will store an unspecified address
     * @param[in] addrAsString The std::string representation of the address
     */
    IPv4Address(const std::string &addrAsString);

    // 拷贝构造
    IPv4Address(const IPv4Address &addr) { m_addr32 = addr.m_addr32; }

    /**
     * Converts the IPv4 address into a 4B integer
     * @return a 4B integer in network byte order representing the IPv4 address
     */
    inline uint32_t toInt() const { return m_addr32; };

    /**
     * Returns a pointer to 4-byte array representing the IPv4 address
     */
    const uint8_t *toBytes() const { return m_addr8; }

    /**
     * Returns a std::string representation of the address
     * @return A string representation of the address
     */
    std::string toString() const;

    uint32_t getHash() const { return m_addr32; }

    /**
     * Determine whether the address is a multicast address
     * @return True if an address is multicast
     */
    bool isMulticast() const;

    /**
     * Determine whether the address is valid (it's not an unspecified/zero)
     * @return True if an address is not unspecified/zero
     */
    bool isValid() const { return m_addr32 != 0; }

    /**
     * Overload of the equal-to operator
     * @param[in] rhs The object to compare with
     * @return True if the addresses are equal, false otherwise
     */
    bool operator==(const IPv4Address &rhs) const { return m_addr32 == rhs.m_addr32; }

    /**
     * Overload of the less-than operator
     * @param[in] rhs The object to compare with
     * @return True if the address value is lower than the other address value, false otherwise
     */
    bool operator<(const IPv4Address &rhs) const { return be32toh(m_addr32) < be32toh(rhs.m_addr32); }

    /**
     * Overload of the not-equal-to operator
     * @param[in] rhs The object to compare with
     * @return True if the addresses are not equal, false otherwise
     */
    bool operator!=(const IPv4Address &rhs) const { return m_addr32 != rhs.m_addr32; }

    /**
     * Checks whether the address matches a subnet.
     * For example: if subnet is 10.1.1.X, subnet mask is 255.255.255.0 and address is 10.1.1.9 then the method will
     * return true Another example: if subnet is 10.1.X.X, subnet mask is 255.0.0.0 and address is 11.1.1.9 then the
     * method will return false
     * @param[in] subnet The subnet to be verified. Notice it's an IPv4Address type, so subnets with don't-cares
     * (like 10.0.0.X) must have some number (it'll be ignored if subnet mask is correct)
     * @param[in] subnetMask A string representing the subnet mask to compare the address with the subnet
     */
    bool matchSubnet(const IPv4Address &subnet, const std::string &subnetMask) const;

    /**
     * Checks whether the address matches a subnet.
     * For example: if subnet is 10.1.1.X, subnet mask is 255.255.255.0 and address is 10.1.1.9 then the method will
     * return true Another example: if subnet is 10.1.X.X, subnet mask is 255.0.0.0 and address is 11.1.1.9 then the
     * method will return false
     * @param[in] subnet The subnet to be verified. Notice it's an IPv4Address type, so subnets with don't-cares
     * (like 10.0.0.X) must have some number (it'll be ignored if subnet mask is correct)
     * @param[in] subnetMask The subnet mask to compare the address with the subnet
     */
    bool matchSubnet(const IPv4Address &subnet, const IPv4Address &subnetMask) const;

    /**
     * A static value representing a zero value of IPv4 address, meaning address of value "0.0.0.0"
     * Notice this value can be omitted in the user code because the default constructor creates an instance with an
     * unspecified/zero address. In order to check whether the address is zero the method isValid can be used
     */
    static const IPv4Address Zero;

    /**
     * A static values representing the lower and upper bound of IPv4 multicast ranges. The bounds are inclusive.
     * MulticastRangeLowerBound is initialized to "224.0.0.0".
     * MulticastRangeUpperBound is initialized to "239.255.255.255".
     * In order to check whether the address is a multicast address the isMulticast method can be used.
     */
    static const IPv4Address MulticastRangeLowerBound;
    static const IPv4Address MulticastRangeUpperBound;

  private:
    union {
        uint32_t m_addr32;
        uint8_t m_addr8[4];
    };
}; // class IPv4Address

/**
 * @class IPv6Address
 * Represents an IPv6 address (of type xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx).
 */
class IPv6Address
{
  public:
    /**
     * A default constructor that creates an instance of the class with unspecified/zero address
     */
    IPv6Address()
    {
        m_addr64[0] = 0;
        m_addr64[1] = 0;
    }

    /**
     * A constructor that creates an instance of the class out of 16-byte array.
     * @param[in] bytes The address as 16-byte array in network byte order
     */
    IPv6Address(const uint8_t bytes[16]) { memcpy(m_addr8, bytes, sizeof(m_addr8)); }

    /**
     * A constructor that creates an instance of the class out of std::string value
     * If the string doesn't represent a valid IPv6 address, an instance will store an unspecified address
     * @param[in] addrAsString The std::string representation of the address
     */
    IPv6Address(const std::string &addrAsString);

    // 拷贝构造
    IPv6Address(const IPv6Address &addr)
    {
        m_addr64[0] = addr.m_addr64[0];
        m_addr64[1] = addr.m_addr64[1];
    }

    /**
     * Returns a pointer to 16-byte array representing the IPv6 address
     */
    const uint8_t *toBytes() const { return m_addr8; }

    /**
     * Returns a std::string representation of the address
     * @return A string representation of the address
     */
    std::string toString() const;

    // hash 通过异或计算
    uint32_t getHash() const { return m_addr32[0] ^ m_addr32[1] ^ m_addr32[2] ^ m_addr32[3]; }

    /**
     * Determine whether the address is a multicast address
     * @return True if an address is multicast
     */
    bool isMulticast() const;

    /**
     * Determine whether the address is unspecified
     */
    bool isValid() const { return *this != Zero; }

    /**
     * Overload of the equal-to operator
     * @param[in] rhs The object to compare with
     * @return True if the addresses are equal, false otherwise
     */
    bool operator==(const IPv6Address &rhs) const
    {
        return (m_addr64[0] == rhs.m_addr64[0]) && (m_addr64[1] == rhs.m_addr64[1]);
    }

    /**
     * Overload of the less-than operator
     * @param[in] rhs The object to compare with
     * @return True if the address value is lower than the other address value, false otherwise
     */
    bool operator<(const IPv6Address &rhs) const
    {
        return ((m_addr64[0] < rhs.m_addr64[0]) ||
                ((m_addr64[0] == rhs.m_addr64[0]) && (m_addr64[1] < rhs.m_addr64[1])));
    }

    /**
     * Overload of the not-equal-to operator
     * @param[in] rhs The object to compare with
     * @return True if the addresses are not equal, false otherwise
     */
    bool operator!=(const IPv6Address &rhs) const { return !(*this == rhs); }

    /**
     * Allocates a byte array and copies address value into it. Array deallocation is user responsibility
     * @param[in] arr A pointer to where array will be allocated
     * @param[out] length Returns the length in bytes of the array that was allocated
     */
    void copyTo(uint8_t **arr, size_t &length) const;

    /**
     * Gets a pointer to an already allocated byte array and copies the address value to it.
     * This method assumes array allocated size is at least 16 (the size of an IPv6 address)
     * @param[in] arr A pointer to the array which address will be copied to
     */
    void copyTo(uint8_t *arr) const { memcpy(arr, m_addr8, sizeof(m_addr8)); }

    /**
     * Checks whether the address matches a subnet.
     * For example: if subnet is 2001:3CA1:010F:001A::, prefixLength is 64, and address is
     * 2001:3CA1:010F:001A:121B:0000:0000:0010, then the method will return true Another example: if subnet is
     * 2001:3CA1:010F:001A::, prefixLength is 70 and address is 2001:3CA1:010F:001A:121B:0000:0000:0010 then the method
     * will return false
     * @param[in] subnet The subnet to be verified
     * @param[in] prefixLength How many bits to use in the mask
     */
    bool matchSubnet(const IPv6Address &subnet, uint8_t prefixLength) const;

    /**
     * A static value representing a zero value of IPv6 address, meaning address of value
     * "0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0" Notice this value can be omitted in the user code because the default
     * constructor creates an instance with an unspecified/zero address. In order to check whether the address is zero
     * the method isValid can be used
     */
    static const IPv6Address Zero;

    /**
     * A static value representing the lower bound of IPv6 multicast ranges. The bound is inclusive.
     * MulticastRangeLowerBound is initialized to "ff00:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0".
     * In order to check whether the address is a multicast address the isMulticast method can be used.
     */
    static const IPv6Address MulticastRangeLowerBound;

  private:
    union {
        uint64_t m_addr64[2];
        uint32_t m_addr32[4];
        uint8_t m_addr8[16];
    };
}; // class IPv6Address

///**
// * @class IPAddress
// * The class is a version-independent representation for an IP address
// */
class IPAddress
{
  public:
    /**
     * An enum representing the address type: IPv4 or IPv6
     */
    enum AddressType {
        /**
         * IPv4 address type
         */
        IPv4AddressType,
        /**
         * IPv6 address type
         */
        IPv6AddressType
    };

    /**
     * A default constructor that creates an instance of the class with unspecified IPv4 address
     */
    IPAddress() : m_Type(IPv4AddressType), m_IPv4((uint32_t)0) {}

    /**
     * A constructor that creates an instance of the class out of IPv4Address.
     * @param[in] addr A const reference to instance of IPv4Address
     */
    IPAddress(const IPv4Address &addr) : m_Type(IPv4AddressType), m_IPv4(addr) {}

    /**
     * A constructor that creates an instance of the class out of IPv6Address.
     * @param[in] addr A const reference to instance of IPv6Address
     */
    IPAddress(const IPv6Address &addr) : m_Type(IPv6AddressType), m_IPv6(addr) {}

    /**
     * A constructor that creates an instance of the class out of std::string value
     * If the string doesn't represent a valid IPv4 or IPv6 address, an instance will store an unspecified address
     * @param[in] addrAsString The std::string representation of the address
     */
    IPAddress(const std::string &addrAsString);

    // 拷贝构造
    IPAddress(const IPAddress &addr)
    {
        m_Type = addr.m_Type;
        if (m_Type == IPv4AddressType) {
            m_IPv4 = addr.m_IPv4;
        } else {
            m_IPv6 = addr.m_IPv6;
        }
    }

    /**
     * Overload of an assignment operator.
     * @param[in] addr A const reference to instance of IPv4Address
     * @return A reference to the assignee
     */
    inline IPAddress &operator=(const IPv4Address &addr);

    /**
     * Overload of an assignment operator.
     * @param[in] addr A const reference to instance of IPv6Address
     * @return A reference to the assignee
     */
    inline IPAddress &operator=(const IPv6Address &addr);

    /**
     * Gets the address type: IPv4 or IPv6
     * @return The address type
     */
    AddressType getType() const { return static_cast<AddressType>(m_Type); }

    uint32_t getHash() const { return (getType() == IPv4AddressType) ? m_IPv4.getHash() : m_IPv6.getHash(); }

    /**
     * Returns a std::string representation of the address
     * @return A string representation of the address
     */
    std::string toString() const { return (getType() == IPv4AddressType) ? m_IPv4.toString() : m_IPv6.toString(); }

    /**
     * @return Determine whether the address is unspecified
     */
    bool isValid() const { return (getType() == IPv4AddressType) ? m_IPv4.isValid() : m_IPv6.isValid(); }

    /**
     * @return Determine whether the object contains an IP version 4 address
     */
    bool isIPv4() const { return getType() == IPv4AddressType; }

    /**
     * @return Determine whether the object contains an IP version 6 address
     */
    bool isIPv6() const { return getType() == IPv6AddressType; }

    /**
     * Determine whether the address is a multicast address
     * @return True if an address is multicast
     */
    bool isMulticast() const { return (getType() == IPv4AddressType) ? m_IPv4.isMulticast() : m_IPv6.isMulticast(); }

    /**
     * Get a reference to IPv4 address instance
     * @return The const reference to IPv4Address instance
     */
    const IPv4Address &getIPv4() const { return m_IPv4; }

    /**
     * Get a reference to IPv6 address instance
     * @return The const reference to IPv6Address instance
     */
    const IPv6Address &getIPv6() const { return m_IPv6; }

    /**
     * Overload of the equal-to operator
     * @param[in] rhs The object to compare with
     * @return True if the addresses are equal, false otherwise
     */
    inline bool operator==(const IPAddress &rhs) const;

    /**
     * Overload of the less-than operator
     * @param[in] rhs The object to compare with
     * @return True if the address value is lower than the other address value, false otherwise
     */
    inline bool operator<(const IPAddress &rhs) const;

    /**
     * Overload of the not-equal-to operator
     * @param[in] rhs The object to compare with
     * @return True if the addresses are not equal, false otherwise
     */
    bool operator!=(const IPAddress &rhs) const { return !(*this == rhs); }

  private:
    uint8_t m_Type;
    union {
        IPv4Address m_IPv4;
        IPv6Address m_IPv6;
    };
};

// implementation of inline methods

bool IPAddress::operator==(const IPAddress &rhs) const
{
    if (isIPv4())
        return rhs.isIPv4() ? (m_IPv4 == rhs.m_IPv4) : false;

    return rhs.isIPv6() ? m_IPv6 == rhs.m_IPv6 : false;
}

bool IPAddress::operator<(const IPAddress &rhs) const
{
    if (isIPv4()) {
        // treat IPv4 as less than IPv6
        // If current obj is IPv4 and other is IPv6 return true
        return rhs.isIPv4() ? (m_IPv4 < rhs.m_IPv4) : true;
    }
    return rhs.isIPv6() ? m_IPv6 < rhs.m_IPv6 : false;
}

IPAddress &IPAddress::operator=(const IPv4Address &addr)
{
    m_Type = IPv4AddressType;
    m_IPv4 = addr;
    return *this;
}

IPAddress &IPAddress::operator=(const IPv6Address &addr)
{
    m_Type = IPv6AddressType;
    m_IPv6 = addr;
    return *this;
}

} // namespace secsmart_tcpip

inline std::ostream &operator<<(std::ostream &os, const secsmart_tcpip::IPv4Address &ipv4Address)
{
    os << ipv4Address.toString();
    return os;
}

inline std::ostream &operator<<(std::ostream &os, const secsmart_tcpip::IPv6Address &ipv6Address)
{
    os << ipv6Address.toString();
    return os;
}

inline std::ostream &operator<<(std::ostream &os, const secsmart_tcpip::IPAddress &ipAddress)
{
    os << ipAddress.toString();
    return os;
}

#endif /* IP_ADDRESS_H */
